# Обзор (Overview)

Box2D - это библиотека 2D-моделирования твердого тела для игр. Программисты могут использовать ее в своих играх, чтобы заставить объекты двигаться реалистично и сделать игровой мир более интерактивным. С точки зрения игрового движка, физический движок - это просто система процедурной анимации.

## Необходимое условие (Prerequisites)

Исходя из этого руководства, я предполагаю, что вы знакомы с основными понятиями физики, такими как масса, сила, крутящий момент и импульсы. Если нет, пожалуйста, сначала обратитесь к поисковой системе Google и Википедии.

Box2D был создан в рамках учебного пособия по физике на Конференции разработчиков игр. Вы можете ознакомиться с этими руководствами в разделе загрузки на сайте [box2d.org](https://box2d.org/).

## Область (Scope)

В данном руководстве описывается большая часть API Box2D. Однако описаны не все аспекты. Пожалуйста, ознакомьтесь с тестовым стендом, входящим в комплект поставки Box2D, чтобы узнать больше.

Данное руководство обновляется только с появлением новых версий. Возможно, последняя версия Box2D не синхронизирована с данным руководством.

## Основные понятия (Core Concepts)

Box2D работает с несколькими фундаментальными концепциями и объектами. Здесь мы кратко описываем эти объекты, а более подробная информация приведена далее в этом документе.

#### shape

Фигура - это двумерный геометрический объект, такой как круг или многоугольник.

#### rigid body

Кусок материи, который настолько прочен, что расстояние между любыми двумя кусочками материи на этом куске постоянно. Они твердые, как алмаз. В дальнейшем мы будем использовать термин "тело" как взаимозаменяемый с термином "твердое тело".

#### fixture

Крепления связывает фигуру с телом и добавляет материалу такие свойства, как плотность, трение и упругость. Крепление помещает фигуру в систему столкновений (широкая фаза), чтобы она могла сталкиваться с другими формами.

#### constraint

Ограничение - это физическая связь, которая устраняет степени свободы у тел. Двумерное тело имеет 3 степени свободы (две координаты перемещения и одну координату вращения). Если мы возьмем тело и прикрепим его к стене (как маятник), мы будем привязывать тело к стене. В этот момент корпус может вращаться только вокруг штифта, таким образом, ограничение устранило 2 степени свободы.

#### contact constraint

Специальное ограничение, предназначенное для предотвращения проникновения твердых тел и симуляции трения и возврата в исходное положение. Вы не создаете ограничения на контакт; они создаются Box2D автоматически.

#### joint

Это ограничение используется для удержания двух или более тел вместе. Box2D поддерживает несколько типов соединений: вращательное, призматическое, дистанционное и другие. Некоторые соединения могут иметь ограничения и приводы.

#### joint limit

Ограничение соединения ограничивает диапазон движений. Например, человеческий локоть допускает только определенный диапазон углов наклона.

#### joint motor

Шарнирное соединение управляет движением соединенных тел в соответствии со степенями свободы. Например, вы можете использовать шарнирное соединение для управления вращением локтя.

#### world

Физический мир - это набор тел, креплений и ограничений, которые взаимодействуют друг с другом. Box2D поддерживает создание нескольких миров, но обычно в этом нет необходимости или желания.

#### solver

В мире физики есть решатель, который используется для опережения времени и устранения ограничений на контакт и соединение. Решатель Box2D - это высокопроизводительный итеративный решатель, который работает в порядке N раз, где N - количество ограничений.

#### continuous collision

Решатель перемещает объекты во времени, используя дискретные временные шаги. Без вмешательства это может привести к туннелированию.

![](./Tunneling_Effect.png)

Box2D содержит специализированные алгоритмы для решения проблемы туннелирования. Во-первых, алгоритмы столкновения могут интерполировать движение двух тел, чтобы определить время первого столкновения (TOI). Во-вторых, существует пошаговое решение, которое перемещает тела к моменту их первого столкновения, а затем устраняет столкновение.

## Модули (Modules)

Box2D состоит из трех модулей: `Common`, `Collision` и `Dynamics`. В модуле `Common` есть код для распределения, математики и настроек. Модуль `Collision` определяет формы, общую фазу и коллизионные функции/запросы. Наконец, модуль `Dynamics` предоставляет мир моделирования, тела, крепления и соединения.

![](./Box2D_Modules.png)

## Единицы измерения (Units)

Box2D работает с числами с плавающей запятой, и для обеспечения хорошей работы Box2D необходимо использовать допуски. Эти допуски были настроены для работы с единицами измерения в метрах, килограммах и секундах (МКС). В частности, Box2D был настроен так, чтобы он хорошо работал с движущимися фигурами размером от 0,1 до 10 метров. Это означает, что объекты размером от банок из-под супа до автобусов должны хорошо работать. Статичные фигуры могут без проблем достигать 50 метров в длину.

Поскольку это 2D физический движок, возникает соблазн использовать пиксели в качестве единиц измерения. К сожалению, это приведет к плохому моделированию и, возможно, странному поведению. Объект длиной 200 пикселей будет восприниматься Box2D как объект размером с 45-этажное здание.

_Внимание: Box2D настроен на использование единиц измерения МКС. Размер движущихся объектов должен составлять примерно от 0,1 до 10 метров. При рендеринге среды и персонажей вам потребуется использовать некоторую систему масштабирования._

Лучше всего рассматривать тела Box2D как движущиеся рекламные щиты, на которые вы прикрепляете свои иллюстрации. Рекламный щит может перемещаться в системе единиц измерения в метрах, но вы можете преобразовать это в пиксельные координаты с помощью простого коэффициента масштабирования. Затем вы можете использовать эти пиксельные координаты для размещения своих спрайтов и т.д. Вы также можете учитывать перевернутые оси координат.

Еще одним ограничением, которое следует учитывать, является общий размер мира. Если размеры ваших объектов в мире будут превышать 2 километра или около того, то потеря точности может повлиять на стабильность.

_Внимание: Box2D лучше всего работает с мирами размером менее 2 километров._

В Box2D углы задаются в радианах. Данные о повороте тела хранятся в радианах и могут увеличиваться неограниченно. Рассмотрите возможность нормализации угла наклона ваших тел, если величина угла становится слишком большой (используйте `Body.SetTransform`).

_Внимание: в Box2D используются радианы, а не градусы._

## Фабрики и определения (Factories and Definitions)

Быстрое управление памятью играет центральную роль в разработке Box2D API. Поэтому, когда вы создаете `Body` или `Joint`, вам нужно вызывать заводские функции в `World`. Никогда не пытайтесь распределять эти типы по-другому.

Есть функции создания:

```C#
Body World.CreateBody(BodyDef def);
Joint World.CreateJoint(JointDef def);
```

И есть соответствующие функции уничтожения:

```C#
void World.DestroyBody(Body body);
void World.DestroyJoint(Joint joint);
```

Когда вы создаете тело или соединение, вам необходимо предоставить определение. Эти определения содержат всю информацию, необходимую для создания тела или соединения. Используя этот подход, мы можем предотвратить ошибки при построении, уменьшить количество функциональных параметров, предоставить разумные значения по умолчанию и сократить количество средств доступа.

Поскольку элементы (фигуры) должны быть привязаны к телу, они создаются и уничтожаются с использованием заводского метода в `Body`:

```C#
Fixture Body.CreateFixture(FixtureDef def);
void Body.DestroyFixture(Fixture fixture);
```

Существует также способ для создания крепления непосредственно на основе формы и плотности.

```C#
Fixture Body.CreateFixture(Shape shape, float density);
```

Фабрики не сохраняют ссылки на определения. Таким образом, вы можете создавать определения в стеке и сохранять их во временных ресурсах.

# Hello Box2D

В дистрибутиве Box2D есть проект Hello World. Программа создает большой наземный блок и маленький динамический блок. Этот код не содержит никакой графики. Все, что вы увидите, - это текст, отображающий положение блока в консоли с течением времени.

Это хороший пример того, как начать работу с Box2D.

## Создание мира (Creating a World)

Каждая программа Box2D начинается с создания объекта `World`. `World` - это физический центр, который управляет памятью, объектами и моделированием. Вы можете разместить физический мир в стеке, куче или разделе данных.

Создать мир Box2D очень просто. Сначала мы определяем вектор силы тяжести.

```C#
Vector2 gravity = new Vector2(0.0f, -10.0f);
```

Теперь мы создаем объект `world`.

```C#
World world = new World(gravity);
```

Итак, теперь у нас есть наш физический мир, давайте начнем добавлять в него кое-что новое.

## Создание заземляющего блока (Creating a Ground Box)

Тела создаются с использованием следующих шагов:

1. Определяется тело с указанием положения, демпфирования и т.д.
2. Используется объект `World` для создания тела.
3. Определяются элементы крепления с помощью формы, трения, плотности и т.д.
4. Создаются элементы крепления на теле.

Для шага 1 мы создаем заземляющее тело. Для этого нам нужно определение заземляющего тела. С помощью определения заземляющего тела мы задаем начальное положение заземляющего тела.

```C#
BodyDef groundBodyDef = new BodyDef();
groundBodyDef.Position = new Vector2(0.0f, -10.0f);
```

На шаге 2 определение тела передается объекту `world` для создания заземляющего тела. Объект `world` не содержит ссылки на определение тела. По умолчанию тела являются статическими. Статические тела не сталкиваются с другими статическими телами и являются неподвижными.

```C#
Body groundBody = world.CreateBody(groundBodyDef);
```

На шаге 3 мы создаем заземляющий многоугольник. Мы используем `SetAsBox`, чтобы придать заземляющему многоугольнику форму прямоугольника, центр которого находится в начале координат родительского тела.

```C#
PolygonShape groundBox = new PolygonShape();
groundBox.SetAsBox(50.0f, 10.0f);
```

Функция `SetAsBox` принимает значения половины ширины и половины высоты (экстенты). Таким образом, в данном случае ширина заземляющего блока составляет 100 единиц (по оси x), а высота - 20 единиц (по оси y). Box2D настроен на метры, килограммы и секунды. Таким образом, вы можете считать, что размеры указаны в метрах. Box2D, как правило, лучше всего работает, когда объекты имеют размер типичных объектов реального мира. Например, высота бочки составляет около 1 метра. Из-за ограничений арифметики с плавающей запятой использование Box2D для моделирования движения ледников или частиц пыли не является хорошей идеей.

На шаге 4 мы завершаем создание заземляющего тела, создав крепление для формы. Для этого шага у нас есть короткий путь. Нам не нужно изменять свойства материала крепления по умолчанию, поэтому мы можем передать форму непосредственно в тело без создания определения крепления. Позже мы увидим, как использовать определение крепления для настройки свойств материала. Второй параметр - это плотность формы в килограммах на квадратный метр. Статическое тело по определению имеет нулевую массу, поэтому плотность в данном случае не используется.

```C#
groundBody.CreateFixture(groundBox, 0.0f);
```

Box2D не сохраняет ссылку на форму. Он клонирует данные в новый объект `Shape`.

Обратите внимание, что у каждого объекта крепления должно быть родительское тело, даже у статических объектов. Однако вы можете прикрепить все статические объекты к одному статическому телу.

Когда вы прикрепляете фигуру к телу с помощью крепления, координаты фигуры становятся локальными для тела. Таким образом, когда тело перемещается, перемещается и фигура. Преобразование мира креплений наследуется от родительского тела. У крепления нет преобразования, независимого от тела. Таким образом, мы не перемещаем фигуру по телу. Перемещение или модификация фигуры, которая находится на теле, не поддерживается. Причина проста: тело с изменяющимися формами не является твердым телом, но Box2D - это движок твердого тела. Многие из допущений, сделанных в Box2D, основаны на модели твердого тела. Если это будет нарушено, многие вещи выйдут из строя.

## Создание динамического тела (Creating a Dynamic Body)

Итак, теперь у нас есть заземляющее тело. Мы можем использовать ту же технику для создания динамического тела. Основное отличие, помимо размеров, заключается в том, что мы должны задать свойства массы динамического тела.

Сначала мы создаем тело с помощью `CreateBody`. По умолчанию тела статичны, поэтому мы должны установить `BodyType` во время создания, чтобы сделать тело динамичным.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.Type = BodyType.Dynamic;
bodyDef.Position = new Vector2(0.0f, 4.0f);
Body body = world.CreateBody(bodyDef);
```

_Внимание: Вы должны задать для типа тела значение `BodyType.Dynamic`, если хотите, чтобы тело двигалось в ответ на воздействие сил._

Затем мы создаем и прикрепляем многоугольную форму, используя определение крепления. Сначала мы создаем прямоугольную форму:

```C#
PolygonShape dynamicBox = new PolygonShape();
dynamicBox.SetAsBox(1.0f, 1.0f);
```

Затем мы создаем определение формы, используя `SetAsBox`. Обратите внимание, что мы установили плотность равной 1. Плотность по умолчанию равна нулю. Кроме того, трение на фигуре установлено равным 0,3.

```C#
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.Shape = dynamicBox;
fixtureDef.Density = 1.0f;
fixtureDef.Friction = 0.3f;
```

_Внимание: Динамическое тело должно содержать по крайней мере один элемент с ненулевой плотностью. В противном случае вы получите странное поведение._

Используя определение крепления, мы теперь можем создать крепление. Это автоматически обновит массу тела. Вы можете добавить к телу столько креплений, сколько захотите. Каждое из них вносит свой вклад в общую массу.

```C#
body.CreateFixture(fixtureDef);
```

Вот и все для инициализации. Теперь мы готовы приступить к моделированию.

## Симуляция мира (Simulating the World)

Итак, мы инициализировали заземляющий блок и динамический блок. Теперь мы готовы предоставить Ньютону возможность заниматься своими делами. Нам осталось решить еще пару вопросов.

Box2D использует вычислительный алгоритм, называемый интегратором. Интеграторы моделируют физические уравнения в дискретные моменты времени. Это соответствует традиционному игровому циклу, в котором у нас, по сути, есть график движения на экране. Поэтому нам нужно выбрать временной шаг для Box2D. Как правило, физические движки для игр поддерживают временной шаг не менее 60 Гц или 1/60 секунды. Вы можете обойтись и большими временными шагами, но вам придется быть более осторожным при настройке определений для вашего мира. Нам также не нравится, когда временной интервал сильно меняется. Переменный временной шаг приводит к переменным результатам, что затрудняет отладку. Поэтому не привязывайте временной шаг к вашей частоте кадров (если только вам это действительно не нужно). Без лишних слов, вот временной шаг.

```C#
float timeStep = 1.0f / 60.0f;
```

В дополнение к интегратору, Box2D также использует более крупный фрагмент кода, называемый решателем ограничений. Решатель ограничений решает все ограничения в симуляции по одному за раз. Одно ограничение может быть решено идеально. Однако, когда мы решаем одно ограничение, мы слегка нарушаем другие ограничения. Чтобы получить хорошее решение, нам нужно выполнить итерацию по всем ограничениям несколько раз.

В программе решения ограничений есть две фазы: фаза скорости и фаза положения. На этапе определения скорости программа вычисляет импульсы, необходимые для правильного перемещения тел. На этапе определения положения программа регулирует положение тел, чтобы уменьшить перекрытие и разъединение стыков. Каждая фаза имеет свое собственное количество итераций. Кроме того, фаза определения местоположения может завершать итерации раньше, если ошибки невелики.

Рекомендуемое количество итераций для Box2D равно 8 для скорости и 3 для положения. Вы можете настроить это число по своему вкусу, просто имейте в виду, что это является компромиссом между производительностью и точностью. Использование меньшего количества итераций повышает производительность, но снижает точность. Аналогично, использование большего количества итераций снижает производительность, но улучшает качество моделирования. Для этого простого примера нам не нужно много итераций. Вот выбранное нами количество итераций.

```C#
int velocityIterations = 6;
int positionIterations = 2;
```

Обратите внимание, что временной шаг и количество итераций совершенно не связаны. Итерация не является подэтапом. Одна итерация решателя - это один проход по всем ограничениям в пределах временного шага. Вы можете выполнить несколько проходов по ограничениям в пределах одного временного шага.

Теперь мы готовы начать цикл моделирования. В вашей игре цикл моделирования может быть объединен с игровым циклом. При каждом прохождении игрового цикла вы вызываете `World.Step`. Обычно достаточно всего одного вызова, в зависимости от вашей частоты кадров и вашего физического временного шага.

Программа Hello World была разработана таким образом, чтобы быть простой, поэтому в ней нет графического вывода. Код выводит положение и поворот динамического тела. Вот цикл моделирования, который имитирует 60 временных шагов в общей сложности за 1 секунду моделируемого времени.

```C#
for (int i = 0; i < 60; ++i)
{
    world.Step(timeStep, velocityIterations, positionIterations);
    Vector2 position = body.GetPosition();
    float angle = body.GetAngle();
    Console.WriteLine($"{position.X:F2} {position.Y:F2} {angle:F2}");
}
```

На выходе показано, как коробка падает и приземляется на землю. Ваш результат должен выглядеть следующим образом:

```
0.00 4.00 0.00
0.00 3.99 0.00
0.00 3.98 0.00
...
0.00 1.25 0.00
0.00 1.13 0.00
0.00 1.01 0.00
```

## Очистка (Cleanup)

Когда объект покидает область видимости, освобождается вся память, зарезервированная для тел, креплений и соединений. Это сделано для повышения производительности и облегчения вашей жизни. Однако вам нужно будет аннулировать все имеющиеся у вас ссылки на тело, крепление или соединение, поскольку они станут недействительными.

# Модуль столкновения (Collision Module)

Модуль `Collision` содержит формы и функции, которые с ними работают. Модуль также содержит динамическое дерево и расширенную фазу для ускорения обработки столкновений в больших системах.

Модуль `Collision` предназначен для использования вне динамической системы. Например, вы можете использовать динамическое дерево для других аспектов вашей игры, помимо физики.

Однако основная цель Box2D - предоставить движок физики твердого тела, поэтому использование модуля коллизий само по себе может показаться ограниченным для некоторых приложений. Точно так же я не буду прилагать особых усилий для документирования этого или доработки API.

## Формы (Shapes)

Фигуры описывают геометрию столкновения и могут использоваться независимо от физического моделирования. Как минимум, вы должны понимать, как создавать фигуры, которые впоследствии могут быть прикреплены к твердым телам.

Фигуры Box2D реализуют базовый класс `Shape`. Базовый класс определяет функции для:

1. Проверяет точку на предмет совпадения с фигурой.
2. Выполняет бросок луча по фигуре.
3. Вычисляет `AABB` фигуры.
4. Вычисляет массовые характеристики фигуры.

Кроме того, у каждой фигуры есть элемент типа и радиус. Радиус применим даже к полигонам, как описано ниже.

Имейте в виду, что фигура не имеет представления о телах и не связана с системой `Dynamics`. Фигуры хранятся в компактной форме, оптимизированной по размеру и производительности. Таким образом, фигуры нелегко перемещать. Для перемещения фигуры необходимо вручную задать положение вершин фигуры. Однако, когда фигура прикреплена к телу с помощью крепления, фигуры перемещаются вместе с основным телом. В итоге:

1. Когда фигура не привязана к телу, вы можете просмотреть ее вершины как выраженные в мировом пространстве.
2. Когда фигура привязана к телу, вы можете просмотреть ее вершины как выраженные в локальных координатах.

### Формы кругов (Circle Shapes)

У окружностей есть положение и радиус. Окружности являются сплошными. Вы не можете создать полый круг, используя форму круга.

```C#
CircleShape circle = new CircleShape();
circle.Center = new Vector2(2.0f, 3.0f);
circle.Radius = 0.5f;
```

### Многоугольные формы (Polygon Shapes)

Многоугольники - это сплошные выпуклые многоугольники. Многоугольник считается выпуклым, когда все отрезки, соединяющие две точки внутри многоугольника, не пересекают ни одного края многоугольника. Многоугольники являются сплошными и никогда не бывают полыми. Многоугольник должен иметь 3 или более вершин.

![](./Convex_and_Concave_Polygons.png)

Вершины многоугольников хранятся с вращением против часовой стрелки (CCW). Мы должны быть осторожны, потому что понятие CCW относится к правой системе координат, в которой ось z направлена за пределы плоскости. На вашем экране это может быть сделано по часовой стрелке, в зависимости от условий вашей системы координат.

![](./Polygon_Winding_Order.png)

Элементы полигона являются общедоступными, но для создания полигона следует использовать функции инициализации. Функции инициализации создают векторы нормалей и выполняют проверку правильности.

Вы можете создать форму полигона, передав массив вершин. Максимальный размер массива определяется параметром `Settings.MaxPolygonVertices`, значение которого по умолчанию равно 8. Этого достаточно для описания большинства выпуклых многоугольников.

Функция `PolygonShape.Set` автоматически вычисляет выпуклую оболочку и устанавливает правильный порядок скручивания. Эта функция работает быстро, когда число вершин невелико. Если вы увеличите число `Settings.MaxPolygonVertices`, то вычисление выпуклой оболочки может замедлиться. Вершины, которые находятся ближе, чем `Settings.LinearSlop`, могут быть объединены.

```C#
// Это определяет треугольник в порядке против часовой стрелки.
Vector2[] vertices = new Vector2[3];
vertices[0] = new Vector2(0.0f, 0.0f);
vertices[1] = new Vector2(1.0f, 0.0f);
vertices[2] = new Vector2(0.0f, 1.0f);

PolygonShape polygon = new PolygonShape();
polygon.Set(vertices);
```

Многоугольная форма обладает некоторыми удобными функциями для создания прямоугольников.

```C#
void SetAsBox(float hx, float hy);
void SetAsBox(float hx, float hy, Vector2 center, float angle);
```

Полигоны наследуют радиус от `Shape`. Радиус создает оболочку вокруг полигона. Оболочка используется в сценариях наложения, чтобы полигоны оставались слегка разделенными. Это позволяет использовать непрерывное столкновение с основным полигоном.

![](./Polygon_Skin.png)

Полигональная оболочка помогает предотвратить туннелирование, разделяя полигоны. В результате получаются небольшие промежутки между фигурами. Ваше визуальное представление может быть больше, чем сам полигон, чтобы скрыть любые промежутки.

![](./Skin_Collision.png)

Это не значит, что полигональная оболочка предназначена только для предотвращения непрерывных столкновений. Цель состоит не в том, чтобы имитировать закругленные полигоны.

### Формы ребер (Edge Shapes)

Формы ребер - это отрезки линий. Они предназначены для создания статичной среды свободной формы для вашей игры. Основным недостатком форм ребер является то, что они могут сталкиваться с кругами и многоугольниками, но не с самими собой. Алгоритмы коллизий, используемые Box2D, требуют, чтобы по крайней мере одна из двух сталкивающихся фигур имела объем. Фигуры с ребрами не имеют объема, поэтому столкновение ребер с ребрами невозможно.

```C#
// Это форма ребра.
Vector2 v1 = new Vector2(0.0f, 0.0f);
Vector2 v2 = new Vector2(1.0f, 0.0f);

EdgeShape edge = new EdgeShape();
edge.SetTwoSided(v1, v2);
```

Во многих случаях игровое окружение создается путем соединения нескольких реберных фигур вплотную друг к другу. Это может привести к возникновению неожиданного артефакта, когда многоугольник скользит вдоль цепочки ребер. На рисунке ниже мы видим прямоугольник, сталкивающийся с внутренней вершиной. Эти призрачные столкновения возникают, когда полигон сталкивается с внутренней вершиной, генерируя нормаль внутреннего столкновения.

![](./Ghost_Collision.png)

Если бы edge1 не существовало, это столкновение выглядело бы нормально. При наличии edge1 внутреннее столкновение выглядит как ошибка. Но обычно, когда Box2D сталкивает две фигуры, он рассматривает их изолированно.

К счастью, форма ребра обеспечивает механизм устранения коллизий-призраков путем сохранения соседних вершин-призраков. Box2D использует эти вершины-призраки для предотвращения внутренних коллизий.

![](./Ghost_Vertices.png)

Алгоритм Box2D для обработки призрачных столкновений поддерживает только одностороннее столкновение. Передняя грань находится справа, если смотреть от первой вершины ко второй вершине. Это соответствует порядку поворота против часовой стрелки, используемому в полигонах.

```C#
// Это реберная фигура с призрачными вершинами.
Vector2 v0 = new Vector2(1.7f, 0.0f);
Vector2 v1 = new Vector2(1.0f, 0.25f);
Vector2 v2 = new Vector2(0.0f, 0.0f);
Vector2 v3 = new Vector2(-1.7f, 0.4f);

EdgeShape edge = new EdgeShape();
edge.SetOneSided(v0, v1, v2, v3);
```

Как правило, сшивание краев таким способом немного расточительно и утомительно. Это подводит нас к форме цепочки.

### Формы цепей (Chain Shapes)

Форма цепочки обеспечивает эффективный способ соединения множества ребер вместе для создания статичных игровых миров. Форма цепочки автоматически устраняет столкновения с призраками и обеспечивает одностороннее столкновение. Столкновение является односторонним для устранения столкновений с призраками.

Если вас не волнуют призрачные столкновения, вы можете просто создать набор фигур с двусторонними ребрами. Эффективность аналогична.

Самый простой способ использовать цепные фигуры - создавать петли. Просто укажите массив вершин.

```C#
Vector2[] vs = new Vector2[4];
vs[0] = new Vector2(1.7f, 0.0f);
vs[1] = new Vector2(1.0f, 0.25f);
vs[2] = new Vector2(0.0f, 0.0f);
vs[3] = new Vector2(-1.7f, 0.4f);

ChainShape chain = new ChainShape();
chain.CreateLoop(vs);
```

Нормаль к краю зависит от порядка намотки. При намотке против часовой стрелки нормаль направлена наружу, а при намотке по часовой стрелке - внутрь.

![](./Chain_Shape_Loop.png)

Возможно, у вас прокручивающийся игровой мир, и вы хотели бы соединить несколько цепочек вместе. Вы можете соединить цепочки, используя призрачные вершины, как мы сделали с `EdgeShape`.

![](./Chain_Shape.png)

```C#
void CreateChain(Vector2[] vertices, Vector2 prevVertex, Vector2 nextVertex)
```

Самопересечение цепочек не поддерживается. Это может сработать, а может и нет. Код, предотвращающий призрачные столкновения, предполагает отсутствие самопересечений в цепочке. Кроме того, очень близкие вершины могут вызвать проблемы. Убедитесь, что все ваши края длиннее, чем `Settings.LinearSlop` (5 мм).

![](./Self_Intersection.png)

Каждое ребро в цепочке рассматривается как дочерняя фигура, к которой можно получить доступ по индексу. Когда фигура цепочки соединяется с телом, каждое ребро получает свою собственную ограничивающую рамку в широком дереве фазовых столкновений.

```C#
// Посещаем каждое дочернее ребро.
ChainShape chain = new ChainShape();
// ...
for (int i = 0; i < chain.GetChildCount(); ++i)
{
    EdgeShape edge = new EdgeShape();
    chain.GetChildEdge(out edge, i);
    // ...
}
```

## Геометрические запросы (Geometric Queries)

Вы можете выполнить несколько геометрических запросов для одной фигуры.

### Испытание точки формы (Shape Point Test)

Вы можете проверить точку на совпадение с фигурой. Вы задаете преобразование для фигуры и мировой точки.

```C#
Transform transform = Transform.Identity;
Vector2 point = new Vector2(5.0f, 2.0f);
bool hit = shape.TestPoint(transform, point);
```

Формы ребер и цепочек всегда возвращают значение `false`, даже если цепочка представляет собой петлю.

### Бросание луча на форму (Shape Ray Cast)

Вы можете направить луч на фигуру, чтобы получить точку первого пересечения и вектор нормали. Для цепочечных фигур используется дочерний индекс, так как луч будет проверять только одно ребро за раз.

_Внимание: попадание не будет зарегистрировано, если луч начинается внутри выпуклой формы, такой как круг или многоугольник. Это согласуется с тем, что Box2D рассматривает выпуклые формы как сплошные._

```C#
Transform transform = Transform.Identity;

RayCastInput input;
input.P1 = new Vector2(0.0f, 0.0f);
input.P2 = new Vector2(1.0f, 0.0f);
input.MaxFraction = 1.0f;
int childIndex = 0;

RayCastOutput output;
bool hit = shape.RayCast(out output, input, transform, childIndex);

if (hit)
{
    Vector2 hitPoint = input.P1 + output.Fraction * (input.P2 - input.P1);
    // ...
}
```

## Попарные функции (Pairwise Functions)

Модуль `Collision` содержит функции, которые принимают пару фигур и вычисляют некоторые результаты. К ним относятся:

1. Overlap (Перекрытие)
2. Contact manifolds (Контактные коллекторы)
3. Distance (Дистанция)
4. Time of impact (Время удара)

### Перекрытие (Overlap)

С помощью этой функции вы можете проверить две фигуры на предмет перекрытия:

```C#
Transform xfA = /* ... */;
Transform xfB = /* ... */;
bool overlap = Contact.TestOverlap(shapeA, indexA, shapeB, indexB, xfA, xfB);
```

Опять же, вы должны указать дочерние индексы для случая цепочечных фигур.

### Контактные коллекторы (Contact Manifolds)

В Box2D есть функции для вычисления точек соприкосновения для перекрывающихся фигур. Если мы рассматриваем круг-круг или круг-многоугольник, мы можем получить только одну точку соприкосновения и нормаль. В случае многоугольник-многоугольник мы можем получить две точки. Эти точки имеют один и тот же вектор нормали, поэтому Box2D группирует их в структуру многообразий. Программа решения контакта использует это для повышения стабильности укладки.

![](./Contact_Manifold.png)

Обычно вам не нужно вычислять контактные многообразия напрямую, однако вы, вероятно, будете использовать результаты, полученные при моделировании.

Структура `Manifold` содержит вектор нормали и до двух точек контакта. Нормаль и точки хранятся в локальных координатах. Для удобства работы контактного решателя каждая точка сохраняет нормальные и тангенциальные импульсы (трение).

Данные, хранящиеся в `Manifold`, оптимизированы для внутреннего использования. Если вам нужны эти данные, обычно лучше всего использовать структуру `WorldManifold` для генерации мировых координат нормали контакта и точек. Вам нужно предоставить `Manifold`, а также преобразования формы и радиусы.

```C#
WorldManifold worldManifold;
worldManifold.Initialize(manifold, transformA, shapeA.Radius, transformB, shapeB.Radius);

for (int i = 0; i < manifold.PointCount; ++i)
{
    Vector2 point = worldManifold.Points[i];
}
```

Обратите внимание, что в `WorldManifold` используется количество точек из исходного многообразия.

Во время моделирования фигуры могут перемещаться, а многообразия - изменяться. Точки могут добавляться или удаляться. Вы можете обнаружить это с помощью `GetPointStates`.

```C#
PointState[] state1;
PointState[] state2;
Global.GetPointStates(out state1, out state2, manifold1, manifold2);

if (state1[0] == PointState.Remove)
{
    // обработка события
}
```

### Дистанция (Distance)

Функция `Contact.Distance` может использоваться для вычисления расстояния между двумя фигурами. Функции `Contact.Distance` необходимо преобразовать обе фигуры в `DistanceProxy`. Также используется некоторое кэширование для предварительного запуска функции `Contact.Distance` при повторных вызовах.

![](./Distance_Function.png)

### Время удара (Time of Impact)

Если две фигуры движутся быстро, они могут проходить друг сквозь друга за один временной шаг.

![](./Tunneling.png)

Функция `TOI.TimeOfImpact` используется для определения времени столкновения двух движущихся фигур. Это называется временем столкновения (TOI). Основная цель функции `TOI.TimeOfImpact` - предотвращение образования туннелей. В частности, она предназначена для предотвращения туннелирования движущихся объектов за пределами геометрии статического уровня.

Эта функция учитывает поворот и перемещение обеих фигур, однако, если повороты достаточно велики, функция может пропустить столкновение. Однако функция по-прежнему будет сообщать о времени без перекрытия и фиксировать все трансляционные коллизии.

Функция времени столкновения определяет начальную разделяющую ось и гарантирует, что фигуры не пересекаются на этой оси. Это может привести к пропуску столкновений, которые очевидны в конечных положениях. Хотя при таком подходе могут быть пропущены некоторые столкновения, он очень быстр и достаточен для предотвращения образования туннелей.

![](./Collision.png)

Сложно установить ограничение на величину поворота. Могут быть случаи, когда столкновения пропускаются при небольших поворотах. Как правило, эти пропущенные столкновения не должны вредить игре. Как правило, это скользящие столкновения.

Для работы функции требуются две фигуры (преобразованные в `DistanceProxy`) и две структуры `Sweep`. Структура `Sweep` определяет начальное и конечное преобразования фигур.

Вы можете использовать фиксированные повороты для выполнения приведения фигуры. В этом случае функция `TOI.TimeOfImpact` не пропустит ни одного столкновения.

## Динамическое дерево (Dynamic Tree)

Класс `DynamicTree` используется Box2D для эффективной организации большого количества фигур. Класс не знает о фигурах. Вместо этого он работает с ориентированными по оси ограничивающими рамками (`AABB`) с пользовательскими данными.

Динамическое дерево - это иерархическое дерево `AABB`. У каждого внутреннего узла в дереве есть два дочерних узла. Конечным узлом является однопользовательский `AABB`. Дерево использует повороты, чтобы поддерживать баланс дерева даже в случае вырожденных входных данных.

Древовидная структура позволяет эффективно выполнять бросание лучей и запросы по регионам. Например, в вашей сцене могут быть сотни фигур. Вы можете выполнить бросание лучей к сцене методом грубой силы, используя бросание лучей к каждой фигуре. Это было бы неэффективно, поскольку не позволяет использовать преимущества разбросанных фигур. Вместо этого вы можете поддерживать динамическое дерево и выполнять бросание лучей к дереву. При этом луч проходит по дереву, пропуская большое количество фигур.

Запрос по регионам использует дерево для поиска всех конечных `AABB`, которые перекрывают запрос `AABB`. Это быстрее, чем метод грубой силы, поскольку многие фигуры могут быть пропущены.

![](./Dynamic_Tree.png)

Обычно вы не будете использовать динамическое дерево напрямую. Вместо этого вы ознакомитесь с классом `World` для приведения лучей и запросов к регионам. Если вы планируете создать собственное динамическое дерево, вы можете узнать, как его использовать, посмотрев, как его использует Box2D.

## Широкая фаза (Broad-phase)

Обработка столкновений на физическом этапе может быть разделена на узкую фазу и широкую фазу. В узкой фазе мы вычисляем точки соприкосновения между парами фигур. Представьте, что у нас есть N фигур. Используя грубую силу, нам нужно было бы выполнить узкую фазу для `N*N/2` пар.

Класс `BroadPhase` снижает эту нагрузку, используя динамическое дерево для управления парами. Это значительно сокращает количество вызовов с узкой фазой.

Обычно вы не взаимодействуете с расширенной фазой напрямую. Вместо этого Box2D создает расширенную фазу и управляет ею внутри системы. Кроме того, `BroadPhase` разработан с учетом цикла моделирования Box2d, поэтому он, вероятно, не подходит для других случаев использования.

# Модуль динамики (Dynamics Module)

Модуль `Dynamics` - это самая сложная часть Box2D, с которой вы, вероятно, чаще всего взаимодействуете. Модуль `Dynamics` расположен поверх модулей `Common` и `Collision`, поэтому вы уже должны быть с ними немного знакомы.

Модуль `Dynamics` содержит:

1. класс fixture (крепление)
2. класс rigid body (твердое тело)
3. класс contact (контакт)
4. классы joint (совместный)
5. класс world (мир)
6. классы listener (слушатель)

Между этими классами существует множество зависимостей, поэтому трудно описать один класс, не ссылаясь на другой. В дальнейшем вы можете увидеть некоторые ссылки на классы, которые еще не были описаны. Поэтому, возможно, вы захотите быстро просмотреть эту главу, прежде чем читать ее внимательно.

Модуль `Dynamics` рассматривается в следующих главах.

## Тела (Bodies)

У тел есть положение и скорость. К телам можно прикладывать силы, крутящие моменты и импульсы. Тела могут быть статическими, кинематическими или динамическими. Ниже приведены определения типов тел:

#### Static

Статическое тело при моделировании не перемещается и ведет себя так, как будто оно имеет бесконечную массу. Внутренне Box2D сохраняет нулевое значение массы и обратную массу. Пользователь может перемещать статические тела вручную. Статическое тело имеет нулевую скорость. Статичные тела не сталкиваются с другими статичными или кинематическими телами.

#### Kinematic

Кинематическое тело при моделировании перемещается в соответствии со своей скоростью. Кинематические тела не реагируют на воздействие сил. Пользователь может перемещать их вручную, но обычно кинематическое тело перемещается, задавая его скорость. Кинематическое тело ведет себя так, как если бы оно обладало бесконечной массой, однако в Box2D масса и обратная масса равны нулю. Кинематические тела не сталкиваются с другими кинематическими или статическими телами.

#### Dynamic

Динамическое тело полностью смоделировано. Пользователь может перемещать его вручную, но обычно оно перемещается под действием сил. Динамическое тело может сталкиваться со всеми типами тел. Динамическое тело всегда имеет конечную ненулевую массу. Если вы попытаетесь обнулить массу динамического тела, оно автоматически приобретет массу в один килограмм и не будет вращаться.

Тела являются основой для крепления (фигур). Тела содержат крепления и перемещают их по миру. В Box2D тела всегда являются твердыми телами. Это означает, что два элемента, прикрепленные к одному и тому же твердому телу, никогда не перемещаются относительно друг друга, и элементы, прикрепленные к одному и тому же телу, не сталкиваются.

Тела имеют геометрию столкновения и плотность. Обычно тела приобретают свои массовые свойства благодаря элементам крепления. Однако вы можете переопределить свойства массы после создания тела.

Обычно вы сохраняете ссылки на все создаваемые вами тела. Таким образом, вы можете запросить позиции тел, чтобы обновить позиции ваших графических объектов. Вам также следует сохранить ссылки на тела, чтобы вы могли уничтожить их, когда закончите работать с ними.

### Определение тела (Body Definition)

Перед созданием тела необходимо создать определение тела (`BodyDef`). Определение тела содержит данные, необходимые для создания и инициализации тела.

Box2D копирует данные из определения тела; он не сохраняет ссылку на определение тела. Это означает, что вы можете повторно использовать определение тела для создания нескольких тел.

Давайте рассмотрим некоторые ключевые элементы определения тела.

### Тип тела (Body Type)

Как обсуждалось в начале этой главы, существует три различных типа тела: статическое, кинематическое и динамическое. Тип тела следует определить при создании, поскольку последующее изменение типа тела сопряжено с большими затратами.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.Type = BodyType.Dynamic;
```

Установка типа тела обязательна.

### Положение и угол наклона (Position and Angle)

Определение тела дает вам возможность инициализировать положение тела при создании. Это намного эффективнее, чем создавать тело в начале мира, а затем перемещать его.

_Внимание: Не создавайте тело в начале координат, а затем перемещайте его. Если вы создадите несколько тел в начале координат, это снизит производительность._

У тела есть две основные точки, представляющие интерес. Первая точка - это начало координат тела. Крепления и стыки прикрепляются относительно начала координат тела. Вторая точка, представляющая интерес, - это центр масс. Центр масс определяется по распределению масс прикрепленных фигур или задается явно с помощью `MassData`. Во многих внутренних вычислениях Box2D используется положение центра масс. Например, в `Body` хранится линейная скорость центра масс.

При построении определения тела вы можете не знать, где находится центр масс. Поэтому вы указываете исходное положение тела. Вы также можете указать угол наклона тела в радианах, на который не влияет положение центра масс. Если вы позже измените массовые свойства тела, то центр масс может переместиться на теле, но исходное положение не изменится, а присоединенные формы и соединения не сдвинутся.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.Position = new Vector2(0.0f, 2.0f); // исходное положение тела.
bodyDef.Angle = 0.25f * MathF.PI; // угол наклона тела в радианах.
```

Твердое тело также является системой отсчета. Вы можете определить крепления и стыки в этой системе отсчета. Эти крепления и узлы крепления никогда не перемещаются в локальной системе отсчета тела.

### Демпфирование (Damping)

Демпфирование используется для уменьшения мировой скорости движения тел. Демпфирование отличается от трения тем, что трение возникает только при контакте. Демпфирование не является заменой трения, и эти два эффекта следует использовать вместе.

Параметры демпфирования должны быть в диапазоне от 0 до бесконечности, где 0 означает отсутствие демпфирования, а бесконечность означает полное демпфирование. Обычно вы должны использовать значение демпфирования от 0 до 0,1. Я обычно не использую линейное демпфирование, потому что при этом тела выглядят так, как будто они плавают.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.LinearDamping = 0.0f;
bodyDef.AngularDamping = 0.01f;
```

Демпфирование рассчитывается приблизительно для обеспечения стабильности и производительности. При малых значениях демпфирования эффект демпфирования в основном не зависит от временного шага. При больших значениях демпфирования эффект демпфирования будет меняться в зависимости от временного шага. Это не проблема, если вы используете фиксированный временной шаг (рекомендуется).

### Масштабирование гравитации (Gravity Scale)

Вы можете использовать масштабирование силы тяжести, чтобы отрегулировать силу тяжести для отдельного тела. Однако будьте осторожны, повышенная сила тяжести может снизить устойчивость.

```C#
// Установить силу гравитации в ноль, чтобы тело плавало
BodyDef bodyDef = new BodyDef();
bodyDef.GravityScale = 0.0f;
```

### Параметры спящего режима (Sleep Parameters)

Что означает сон? Симуляция тела обходится дорого, поэтому чем меньше нам приходится симулировать, тем лучше. Когда тело отдыхает, мы хотели бы прекратить его симулировать.

Когда Box2D определяет, что тело (или группа тел) остановилось, оно переходит в спящий режим, который требует очень небольшой нагрузки на процессор. Если тело бодрствует и сталкивается со спящим телом, то спящее тело просыпается. Тела также просыпаются, если повреждено соединение или контакт, прикрепленный к ним. Вы также можете разбудить тело вручную.

Определение тела позволяет указать, может ли тело находиться в спящем режиме и создается ли оно в спящем режиме.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.AllowSleep = true;
bodyDef.Awake = true;
```

### Фиксированное вращение (Fixed Rotation)

Вы можете захотеть, чтобы жесткое тело, например персонаж, имело фиксированное вращение. Такое тело не должно вращаться даже под нагрузкой. Для достижения этой цели вы можете использовать параметр фиксированного вращения:

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.FixedRotation = true;
```

Флажок фиксированного вращения приводит к тому, что инерция вращения и ее обратная величина обнуляются.

### Пули (Bullets)

Игровое моделирование обычно генерирует последовательность изображений, которые воспроизводятся с определенной частотой кадров. Это называется дискретным моделированием. При дискретном моделировании твердые тела могут перемещаться на большую величину за один временной шаг. Если физический движок не учитывает большое движение, вы можете увидеть, что некоторые объекты неправильно проходят друг сквозь друга. Этот эффект называется туннелированием.

По умолчанию Box2D использует функцию непрерывного обнаружения столкновений (CCD), чтобы предотвратить туннелирование динамических объектов через статические. Это делается путем перемещения фигур из их старого положения в новое. Движок ищет новые столкновения во время развертки и вычисляет время столкновения (TOI) для этих столкновений. Тела перемещаются в их первый TOI, а затем вычислитель выполняет вспомогательный шаг для завершения полного временного шага. В рамках подэтапа могут быть дополнительные события TOI.

Обычно ПЗС-матрица не используется между динамическими объектами. Это делается для обеспечения приемлемой производительности. В некоторых игровых сценариях для использования ПЗС-матрицы вам нужны динамические объекты. Например, вы можете захотеть запустить высокоскоростную пулю в стопку динамических кирпичей. Без ПЗС пуля может пробить туннель сквозь кирпичи.

Быстро движущиеся объекты в Box2D можно обозначить как пули. Пули будут выполнять функцию ПЗС как со статическими, так и с динамическими телами. Вы должны решить, какие тела должны быть пулями, исходя из вашего игрового дизайна. Если вы решите, что тело следует рассматривать как пулю, используйте следующую настройку.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.Bullet = true;
```

Флажок с маркером влияет только на динамические тела.

### Активация (Activation)

Вы можете захотеть, чтобы тело было создано, но не участвовало в столкновении или динамике. Это состояние похоже на сон, за исключением того, что тело не будет разбужено другими телами и крепления тела не будут размещены в широкой фазе. Это означает, что тело не будет участвовать в столкновениях, отбрасывании лучей и т.д.

Вы можете создать тело в неактивном состоянии и позже повторно активировать его.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.Enabled = true;
```

Соединения могут быть связаны с неактивными телами. Эти соединения не будут имитироваться. При активации тела необходимо следить за тем, чтобы его соединения не были искажены.

Обратите внимание, что активация тела обходится почти так же дорого, как и создание тела с нуля. Поэтому вам не следует использовать активацию для потоковой передачи миров. Используйте создание / разрушение потоковых миров для экономии памяти.

### Данные пользователя (User Data)

Пользовательские данные - это любой объект. Это дает вам возможность связать объекты вашего приложения с телами. Вы должны быть последовательны, чтобы использовать один и тот же тип объекта для всех пользовательских данных тела.

```C#
BodyDef bodyDef = new BodyDef();
bodyDef.UserData = new object();
```

### Фабрика тел (Body Factory)

Тела создаются и уничтожаются с помощью фабрики тел, предоставляемой классом `World`. Это позволяет миру создавать тела с помощью эффективного распределителя и добавлять тела в структуру данных мира.

```C#
World myWorld = new World();
BodyDef bodyDef = new BodyDef();
// ...
Body? dynamicBody = myWorld.CreateBody(bodyDef);
// ... делаем работу ...
myWorld.DestroyBody(dynamicBody);
dynamicBody = null;
```

Box2D не хранит ссылки на определение тела или какие-либо данные, которые в нем содержатся (за исключением ссылок на пользовательские данные). Таким образом, вы можете создавать временные определения тела и повторно использовать одни и те же определения тела.

Box2D позволяет вам избежать уничтожения тел, удалив ваш объект `World`, который выполнит всю работу по очистке за вас. Однако вам следует помнить об отмене ссылок на тела, которые вы сохраняете в своем игровом движке.

При разрушении тела автоматически уничтожаются прикрепленные элементы крепления и стыки. Это имеет важное значение для управления ссылок на формы и стыки.

### Использование тела (Using a Body)

После создания тела с ним можно выполнить множество операций. К ним относятся настройка свойств массы, доступ к местоположению и скорости, применение сил и преобразование точек и векторов.

### Массовые данные (Mass Data)

Тело имеет массу (скалярную), центр масс (векторную) и инерцию вращения (скалярную). Для неподвижных тел масса и инерция вращения равны нулю. Когда тело имеет фиксированное вращение, его инерция вращения равна нулю.

Обычно свойства массы тела устанавливаются автоматически при добавлении к нему креплений. Вы также можете настроить массу тела во время выполнения. Обычно это делается, когда у вас есть специальные игровые сценарии, требующие изменения массы.

```C#
void Body.SetMassData(MassData massData);
```

После прямой настройки массы тела вы можете захотеть вернуться к естественной массе, заданной креплениями. Вы можете сделать это с помощью:

```C#
void Body.ResetMassData();
```

Данные о массе тела доступны с помощью следующих функций:

```C#
float Body.GetMass();
float Body.GetInertia();
Vector2 Body.GetLocalCenter();
void Body.GetMassData(out MassData data);
```

### Информация о состоянии (State Information)

Состояние тела зависит от многих факторов. Вы можете эффективно получить доступ к этим данным о состоянии с помощью следующих функций:

```C#
void Body.SetType(BodyType type);
BodyType Body.GetType();
void Body.SetBullet(bool flag);
bool Body.IsBullet();
void Body.SetSleepingAllowed(bool flag);
bool Body.IsSleepingAllowed();
void Body.SetAwake(bool flag);
bool Body.IsAwake();
void Body.SetEnabled(bool flag);
bool Body.IsEnabled();
void Body.SetFixedRotation(bool flag);
bool Body.IsFixedRotation();
```

### Положение и скорость (Position and Velocity)

Вы можете получить доступ к положению и повороту тела. Это часто используется при визуализации связанного с вами игрового персонажа. Вы также можете задать положение, хотя это менее распространено, поскольку обычно вы используете Box2D для симуляции движения.

```C#
bool Body.SetTransform(Vector2 position, float angle);
Transform Body.GetTransform();
Vector2 Body.GetPosition();
float Body.GetAngle();
```

Вы можете получить доступ к положению центра масс в локальных и мировых координатах. Большая часть внутреннего моделирования в Box2D использует центр масс. Однако, как правило, доступ к нему не требуется. Вместо этого вы обычно будете работать с преобразованием тела. Например, у вас может быть квадратное тело. Исходной точкой тела может быть угол квадрата, в то время как центр масс расположен в центре квадрата.

```C#
Vector2 Body.GetWorldCenter();
Vector2 Body.GetLocalCenter();
```

Вы можете получить доступ к линейной и угловой скорости. Линейная скорость относится к центру масс. Следовательно, линейная скорость может изменяться при изменении свойств массы.

### Силы и импульсы (Forces and Impulses)

К телу можно прикладывать силы, крутящие моменты и импульсы. Когда вы прикладываете силу или импульс, вы создаете точку в мире, к которой прикладывается нагрузка. Это часто приводит к возникновению крутящего момента вокруг центра масс.

```C#
void Body.ApplyForce(Vector2 force, Vector2 point);
void Body.ApplyTorque(float torque);
void Body.ApplyLinearImpulse(Vector2 impulse, Vector2 point);
void Body.ApplyAngularImpulse(float impulse);
```

Приложение силы, крутящего момента или импульса пробуждает тело. Иногда это нежелательно. Например, вы можете прикладывать постоянное усилие и хотите дать телу поспать, чтобы улучшить производительность. В этом случае вы можете использовать следующий код.

```C#
if (myBody.IsAwake() == true)
    myBody.ApplyForce(myForce, myPoint);
```

### Преобразования координат (Coordinate Transformations)

В классе тела есть несколько полезных функций, которые помогут вам преобразовать точки и векторы между локальным и мировым пространством. Если вы не понимаете этих понятий, пожалуйста, прочитайте "Основы математики для игр и интерактивных приложений" Джима Ван Верта и Ларса Бишопа. Эти функции эффективны (если они встроены).

```C#
Vector2 Body.GetWorldPoint(Vector2 localPoint);
Vector2 Body.GetWorldVector(Vector2 localVector);
Vector2 Body.GetLocalPoint(Vector2 worldPoint);
Vector2 Body.GetLocalVector(Vector2 worldVector);
```

### Обработка креплений, соединений и контактов (Acessing Fixtures, Joints, and Contacts)

Вы можете выполнять итерацию по креплениям тела. Это в основном полезно, если вам нужно получить доступ к пользовательским данным текущих креплений.

```C#
for (Fixture f = body.GetFixtureList(); f != null; f = f.GetNext())
{
    MyFixtureData data = (MyFixtureData)f.UserData;
    // делаем что-то с данными...
}
```

Аналогичным образом вы можете выполнить итерацию по списку соединений тела.

В теле также содержится список связанных контактов. Вы можете использовать это для получения информации о текущих контактах. Будьте осторожны, поскольку список контактов может содержать не все контакты, которые существовали на предыдущем временном шаге.

## Крепления (Fixtures)

Напомним, что фигуры не связаны с телами и могут использоваться независимо от физического моделирования. Поэтому Box2D предоставляет класс `Fixture` для присоединения фигур к телам. Тело может иметь ноль или более креплений. Тело с несколькими креплениями иногда называют составным телом.

Крепления содержат следующее:

1. единая форма (a single shape)
2. широкофазные прокси (broad-phase proxies)
3. плотность, трение и реституция (density, friction, and restitution)
4. флаги фильтрации столкновений (collision filtering flags)
5. обратная ссылка на родительское тело (back pointer to the parent body)
6. пользовательские данные (user data)
7. флаг сенсора (sensor flag)

Они описаны в следующих разделах.

### Создание крепления (Fixture Creation)

Крепления создаются путем инициализации определения крепления и последующей передачи этого определения в родительский объект.

```C#
Body myBody;
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.Shape = myShape;
fixtureDef.Density = 1.0f;
Fixture myFixture = myBody.CreateFixture(fixtureDef);
```

При этом создается крепление и оно прикрепляется к телу. Вам не нужно сохранять ссылку на крепление, поскольку крепление будет автоматически уничтожено при уничтожении родительского тела. Вы можете создать несколько креплений на одном теле.

Вы можете уничтожить крепление на родительском теле. Вы можете сделать это, чтобы смоделировать хрупкий объект. В противном случае вы можете просто оставить крепление в покое и позволить программе уничтожения тела позаботиться об уничтожении креплений.

```C#
myBody.DestroyFixture(myFixture);
```

### Плотность (Density)

Плотность крепления используется для расчета массовых характеристик основного элемента. Плотность может быть нулевой или положительной. Как правило, для всех элементов крепления следует использовать одинаковые значения плотности. Это улучшит стабильность укладки.

Масса тела не регулируется при установке плотности. Для этого необходимо вызвать `ResetMassData`.

```C#
Fixture fixture;
fixture.Density = 5.0f;
Body body;
body.ResetMassData();
```

### Трение (Friction)

Трение используется для реалистичного скольжения объектов друг по другу. Box2D поддерживает статическое и динамическое трение, но использует один и тот же параметр для обоих. Трение в Box2D моделируется точно, а сила трения пропорциональна нормальной силе (это называется кулоновским трением). Параметр трения обычно устанавливается в диапазоне от 0 до 1, но может принимать любое неотрицательное значение. Значение трения, равное 0, отключает трение, а значение, равное 1, усиливает трение. Когда рассчитывается сила трения между двумя фигурами, Box2D должен объединить параметры трения двух родительских элементов. Это делается со средним геометрическим значением:

```C#
Fixture fixtureA;
Fixture fixtureB;
float friction = MathF.Sqrt(fixtureA.Friction * fixtureB.Friction);
```

Таким образом, если одно крепление имеет нулевое трение, то и контакт будет иметь нулевое трение.

Вы можете переопределить значение смешанного трения по умолчанию, используя `Contact.SetFriction`. Обычно это делается в обратном вызове `ContactListener`.

### Реституция (Restitution)

Реституция используется для того, чтобы заставить предметы отскакивать. Значение реституции обычно устанавливается в диапазоне от 0 до 1. Попробуйте уронить мяч на стол. Нулевое значение означает, что мяч не отскочит. Это называется неупругим столкновением. Значение, равное единице, означает, что скорость мяча будет точно отражена. Это называется идеально упругим столкновением. Компенсация складывается по следующей формуле.

```C#
Fixture fixtureA;
Fixture fixtureB;
float restitution = MathF.Max(fixtureA.Restitution, fixtureB.Restitution);
```

Реституция объединена таким образом, что у вас может быть надувной супербол без надувного пола.

Вы можете переопределить стандартную смешанную реституцию, используя `Contact.SetRestitution`. Обычно это делается в обратном вызове `ContactListener`.

Когда фигура образует несколько контактов, реституция моделируется приблизительно. Это связано с тем, что Box2D использует итеративный решатель. Box2D также использует неупругие столкновения, когда скорость столкновения мала. Это сделано для предотвращения дрожания. Смотрите порог скорости `Settings.VelocityThreshold`.

### Фильтрация (Filtering)

Фильтрация столкновений позволяет предотвратить столкновения между объектами. Например, допустим, вы создаете персонажа, который ездит на велосипеде. Вы хотите, чтобы велосипед сталкивался с рельефом, а персонаж - с рельефом, но вы не хотите, чтобы персонаж сталкивался с велосипедом (потому что они должны пересекаться). Box2D поддерживает фильтрацию таких столкновений с помощью категорий и групп.

Box2D поддерживает 16 категорий коллизий. Для каждого устройства вы можете указать, к какой категории оно относится. Вы также указываете, с какими другими категориями может сталкиваться это устройство. Например, вы могли бы указать в многопользовательской игре, что все игроки не должны сталкиваться друг с другом, а монстры не должны сталкиваться друг с другом, но игроки и монстры должны сталкиваться. Это делается с помощью маскирующих элементов. Например:

```C#
FixtureDef playerFixtureDef, monsterFixtureDef;
playerFixtureDef.Filter.CategoryBits = 0x0002;
monsterFixtureDef.Filter.CategoryBits = 0x0004;
playerFixtureDef.Filter.MaskBits = 0x0004;
monsterFixtureDef.Filter.MaskBits = 0x0002;
```

Вот правило, по которому должно произойти столкновение:

```C#
ushort catA = fixtureA.FilterData.CategoryBits;
ushort maskA = fixtureA.FilterData.MaskBits;
ushort catB = fixtureB.FilterData.CategoryBits;
ushort maskB = fixtureB.FilterData.MaskBits;

if ((catA & maskB) != 0 && (catB & maskA) != 0)
{
    // крепления могут сталкиваться
}
```

Группы столкновений позволяют задать интегральный групповой индекс. Вы можете настроить так, чтобы все приспособления с одинаковым групповым индексом всегда сталкивались (положительный индекс) или никогда не сталкивались (отрицательный индекс). Групповые индексы обычно используются для объектов, которые каким-либо образом связаны, например, для деталей велосипеда. В следующем примере `fixture1` и `fixture2` всегда сталкиваются, но `fixture3` и `fixture4` никогда не сталкиваются.

```C#
fixture1Def.Filter.GroupIndex = 2;
fixture2Def.Filter.GroupIndex = 2;
fixture3Def.Filter.GroupIndex = -8;
fixture4Def.Filter.GroupIndex = -8;
```

Коллизии между объектами с разными групповыми индексами фильтруются в соответствии с битами категории и маски. Другими словами, групповая фильтрация имеет более высокий приоритет, чем фильтрация по категориям.

Обратите внимание, что в Box2D выполняется дополнительная фильтрация коллизий. Вот список:

1. Крепление на статичном теле может сталкиваться только с динамическим телом.
2. Крепление на кинематическом теле может сталкиваться только с динамическим телом.
3. Крепления на одном теле никогда не сталкиваются друг с другом.
4. При желании вы можете включить/отключить столкновение между креплениями на телах, соединенных шарниром.

Иногда может потребоваться изменить фильтрацию коллизий после того, как объект уже создан. Вы можете получить и настроить структуру `Filter` для существующего объекта, используя `Fixture.GetFilterData` и `Fixture.SetFilterData`. Обратите внимание, что изменение данных фильтра не приведет к добавлению или удалению контактов до следующего временного шага (см. Мировой класс).

### Сенсоры (Sensors)

Иногда игровой логике нужно знать, когда два устройства перекрываются, но реакции на столкновение быть не должно. Это делается с помощью датчиков. Датчик - это устройство, которое обнаруживает столкновение.

Вы можете пометить любое устройство как датчик. Датчики могут быть статическими, кинематическими или динамическими. Помните, что у вас может быть несколько креплений на одно тело, и вы можете использовать любое сочетание датчиков и стационарных креплений. Кроме того, датчики подключаются только к контактам, когда хотя бы одно тело является динамическим, поэтому вы не получите контакта между кинематическим и статическим, кинематическим и статическим или статическим и статическим телами.

Датчики не создают точек контакта. Существует два способа узнать состояние датчика:

1. `Contact.IsTouching`
2. `ContactListener.BeginContact` и `ContactListener.EndContact`

## Соединения (Joints)

Соединения используются для того, чтобы привязать тела к окружающему миру или друг к другу. Типичными примерами в играх являются тряпичные куклы, качели и блоки. Соединения можно комбинировать различными способами для создания интересных движений.

Некоторые соединения обеспечивают ограничения, позволяющие контролировать амплитуду движений. В некоторых соединениях предусмотрены шарнирные соединения, которые могут использоваться для приведения соединения в движение с заданной скоростью до тех пор, пока не будет превышено заданное усилие/крутящий момент.

Шарнирные соединения можно использовать по-разному. Вы можете использовать шарнирные соединения для управления положением, задав скорость вращения шарнира, пропорциональную разнице между фактическим и желаемым положением. Вы также можете использовать шарнирные соединения для симуляции трения в шарнире: установите скорость вращения шарнира на ноль и обеспечьте небольшое, но значительное максимальное усилие шарнирного соединения/крутящий момент. Затем шарнирное соединение будет пытаться удерживать шарнир от перемещения до тех пор, пока нагрузка не станет слишком большой.

### Определение соединения (Joint Definition)

Каждый тип соединения имеет свое определение, полученное из `JointDef`. Все соединения устанавливаются между двумя разными телами. Одно тело может быть статичным. Соединения между статичными и/или кинематическими телами допускаются, но не имеют эффекта и требуют некоторого времени обработки.

Вы можете указать пользовательские данные для любого типа соединения и установить флажок, предотвращающий столкновение подключенных тел друг с другом. На самом деле это поведение по умолчанию, и вы должны задать логическое значение `CollideConnected`, чтобы разрешить столкновение между подключенными телами.

Для определения многих соединений требуется указать некоторые геометрические данные. Часто соединение определяется опорными точками. Это точки, зафиксированные в прикрепленных телах. Для Box2D требуется, чтобы эти точки были указаны в локальных координатах. Таким образом, соединение может быть задано даже в том случае, если текущие преобразования тел нарушают ограничение на соединение - обычное явление при сохранении и перезагрузке игры. Кроме того, для некоторых определений соединений необходимо знать относительный угол между телами по умолчанию. Это необходимо для правильного ограничения поворота.

Инициализация геометрических данных может быть трудоемкой, поэтому во многих соединениях есть функции инициализации, которые используют текущие преобразования тела, что избавляет от большей части работы. Однако эти функции инициализации обычно следует использовать только для создания прототипов. Рабочий код должен непосредственно определять геометрию. Это сделает поведение соединения более надежным.

Остальные данные для определения соединения зависят от типа соединения. Мы рассмотрим их сейчас.

### Фабрика соединений (Joint Factory)

Соединения создаются и уничтожаются с использованием методов фабрики `World`. Вот пример жизненного цикла вращающегося шарнира:

```C#
Body myBodyA, myBodyB;
World myWorld;
RevoluteJointDef jointDef;
jointDef.BodyA = myBodyA;
jointDef.BodyB = myBodyB;
jointDef.LocalAnchorA = myBodyA.Position;

RevoluteJoint? joint = (RevoluteJoint)myWorld.CreateJoint(jointDef);

// ... делаем работу ...

myWorld.DestroyJoint(joint);
joint = null;
```

Всегда полезно обнулять ссылку после того, как они будут уничтожены. Это приведет к контролируемому завершению работы программы, если вы попытаетесь повторно использовать ссылку.

Срок службы соединения не так прост. Обратите внимание на это предупреждение:

_Внимание: Соединения разрушаются при разрушении прикрепленного тела._

Эта мера предосторожности не всегда необходима. Вы можете организовать свой игровой движок таким образом, чтобы соединения всегда уничтожались раньше прикрепленных тел. В этом случае вам не нужно реализовывать класс слушателя. Подробности смотрите в разделе о неявном уничтожении.

### Использование соединений (Using Joints)

Во многих моделированиях стыки создаются и используются до тех пор, пока они не будут уничтожены. Однако в стыках содержится много полезных данных, которые можно использовать для создания расширенной симуляции.

Прежде всего, вы можете получить тела, точки привязки и пользовательские данные из стыка.

```C#
Body Joint.GetBodyA();
Body Joint.GetBodyB();
Vector2 Joint.GetAnchorA();
Vector2 Joint.GetAnchorB();
void Joint.GetUserData();
```

Все шарниры имеют силу реакции и крутящий момент. Это сила реакции, приложенная к телу 2 в точке привязки. Вы можете использовать силу реакции для разрушения шарниров или запуска других игровых событий. Эти функции могут выполнять некоторые вычисления, поэтому не вызывайте их, если вам не нужен результат.

```C#
Vector2 Joint.GetReactionForce();
float Joint.GetReactionTorque();
```

### Дистанционное соединение (Distance Joint)

Одним из простейших соединений является дистанционное соединение, в котором говорится, что расстояние между двумя точками на двух телах должно быть постоянным. Когда вы задаете дистанционное соединение, два тела уже должны быть на месте. Затем вы указываете две опорные точки в мировых координатах. Первая точка привязки соединена с телом 1, а вторая точка привязки соединена с телом 2. Эти точки означают длину ограничения расстояния.

![](./Distance_Joint.png)

Вот пример определения расстояния между соединениями. В данном случае мы решили допустить столкновение тел.

```C#
Body myBodyA, myBodyB;
Vector2 worldAnchorOnBodyA, worldAnchorOnBodyB;
DistanceJointDef jointDef = new DistanceJointDef();
jointDef.Initialize(myBodyA, myBodyB, worldAnchorOnBodyA, worldAnchorOnBodyB);
jointDef.CollideConnected = true;
```

Дистанционное соединение также может быть выполнено мягким, как пружинно-демпферное соединение. Смотрите пример полотна на испытательном стенде, чтобы увидеть, как оно работает.

Мягкость достигается за счет настройки двух констант в определении: жесткости и демпфирования. Непосредственная установка этих значений может быть неинтуитивной, поскольку они выражаются в ньютонах. Box2D предоставляет API для вычисления этих значений с точки зрения частоты и коэффициента демпфирования.

```C#
public static void Joint.LinearStiffness(
	out float stiffness,
	out float damping,
    float frequencyHz,
	float dampingRatio,
	Body bodyA,
	Body bodyB)
```

Думайте о частоте как о частоте гармонического генератора (например, гитарной струны). Частота указывается в герцах. Обычно частота должна быть меньше половины частоты временного шага. Таким образом, если вы используете временной шаг 60 Гц, частота соединения расстояний должна быть меньше 30 Гц. Причина связана с частотой Найквиста.

Коэффициент демпфирования не имеет размерности и обычно находится в диапазоне от 0 до 1, но может быть и больше. При значении 1 демпфирование имеет решающее значение (все колебания должны исчезнуть).

```C#
float frequencyHz = 4.0f;
float dampingRatio = 0.5f;
Joint.LinearStiffness(jointDef.stiffness, jointDef.damping, frequencyHz, dampingRatio, jointDef.bodyA, jointDef.bodyB);
```

Также можно задать минимальную и максимальную длину для промежуточного соединения. Подробности смотрите в разделе `DistanceJointDef`.

### Вращающийся шарнир (Revolute Joint)

Вращающееся соединение заставляет два тела располагаться в общей точке крепления, часто называемой точкой шарнира. Вращающееся соединение имеет единственную степень свободы: относительное вращение двух тел. Это называется углом соединения.

![](./Revolute_Joint.png)

Чтобы задать поворот, вам необходимо указать два тела и одну точку привязки в мировом пространстве. Функция инициализации предполагает, что тела уже находятся в правильном положении.

В этом примере два тела соединены шарниром поворота в центре масс первого тела.

```C#
Body myBodyA, myBodyB;
RevoluteJointDef jointDef;
jointDef.Initialize(myBodyA, myBodyB, myBodyA.GetWorldCenter());
```

Угол поворота шарнира положителен, когда тело поворачивается против часовой стрелки вокруг угловой точки. Как и все углы в Box2D, угол поворота измеряется в радианах. Обычно угол поворота стыка равен нулю, когда соединение создается с помощью функции `Initialize()`, независимо от текущего поворота двух тел.

В некоторых случаях может потребоваться управлять углом соединения. Для этого поворотный шарнир может дополнительно имитировать ограничитель соединения и/или шарнирное соединение.

Предельный угол соединения должен оставаться между нижней и верхней границами. Для достижения этого предельного значения будет применяться столько крутящего момента, сколько необходимо. Предельный диапазон должен включать ноль, в противном случае при моделировании соединение будет наклоняться.

Шарнирный двигатель позволяет задать скорость вращения (производную от угла по времени). Скорость может быть отрицательной или положительной. Двигатель может обладать бесконечным усилием, но обычно это нежелательно. Вспомним вечный вопрос:

_Что происходит, когда непреодолимая сила сталкивается с неподвижным объектом?_

Я могу сказать вам, что это не очень удобно. Таким образом, вы можете обеспечить максимальный крутящий момент для шарнирного двигателя. Шарнирный двигатель будет поддерживать заданную частоту вращения до тех пор, пока требуемый крутящий момент не превысит указанный максимальный. При превышении максимального крутящего момента шарнир замедлится и даже может дать задний ход.

Вы можете использовать шарнирный двигатель для симуляции трения в шарнире. Просто установите скорость вращения шарнира на ноль, а максимальный крутящий момент - на небольшое, но значимое значение. Двигатель попытается предотвратить вращение шарнира, но будет работать при значительной нагрузке.

Приведенное выше определение вращающегося шарнира изменено; на этот раз шарнир имеет ограничение и включен двигатель. Двигатель настроен для симуляции трения в соединении.

```C#
RevoluteJointDef jointDef;
jointDef.Initialize(bodyA, bodyB, myBodyA.GetWorldCenter());
jointDef.LowerAngle = -0.5f * MathF.PI; // -90 градусов
jointDef.UpperAngle = 0.25f * MathF.PI; // 45 градусов
jointDef.EnableLimit = true;
jointDef.MaxMotorTorque = 10.0f;
jointDef.MotorSpeed = 0.0f;
jointDef.EnableMotor = true;
```

Вы можете получить доступ к углу поворота шарнира, скорости вращения и крутящему моменту двигателя.

```C#
float RevoluteJoint.GetJointAngle();
float RevoluteJoint.GetJointSpeed();
float RevoluteJoint.GetMotorTorque();
```

Вы также обновляете параметры двигателя на каждом этапе.

```C#
void RevoluteJoint.SetMotorSpeed(float speed);
void RevoluteJoint.SetMaxMotorTorque(float torque);
```

Шарнирные двигатели обладают некоторыми интересными возможностями. Вы можете изменять скорость вращения шарнира на каждом временном шаге, чтобы заставить шарнир двигаться вперед-назад по синусоиде или в соответствии с любой другой функцией, которую вы хотите.

```C#
// ... Игровой цикл начался ...

myJoint.SetMotorSpeed(MathF.Cos(0.5f * time));

// ... Игровой цикл закончился ...
```

Вы также можете использовать шарнирные двигатели для определения желаемого угла соединения. Например:

```C#
// ... Игровой цикл начался ...

float angleError = myJoint.GetJointAngle() - angleTarget;
float gain = 0.1f;
myJoint.SetMotorSpeed(-gain * angleError);

// ... Игровой цикл закончился ...
```

Как правило, ваш коэффициент усиления не должен быть слишком большим. В противном случае ваше соединение может стать нестабильным.

### Призматический шарнир (Prismatic Joint)

Призматическое соединение обеспечивает относительное перемещение двух тел вдоль заданной оси. Призматическое соединение предотвращает относительное вращение. Таким образом, призматическое соединение имеет единственную степень свободы.

![](./Prismatic_Joint.png)

Определение призматического соединения аналогично описанию вращающегося соединения; просто замените угол на перемещение, а крутящий момент - на силу. Используя эту аналогию, приведем пример определения призматического соединения с ограничением соединения и двигателем трения:

```C#
PrismaticJointDef jointDef;
Vector2 worldAxis = new Vector2(1.0f, 0.0f);
jointDef.Initialize(myBodyA, myBodyB, myBodyA.GetWorldCenter(), worldAxis);
jointDef.LowerTranslation = -5.0f;
jointDef.UpperTranslation = 2.5f;
jointDef.EnableLimit = true;
jointDef.MaxMotorForce = 1.0f;
jointDef.MotorSpeed = 0.0f;
jointDef.EnableMotor = true;
```

Вращающееся соединение имеет неявную ось, выходящую за пределы экрана. Призматическое соединение нуждается в явной оси, параллельной экрану. Эта ось закреплена в двух телах и повторяет их движение.

Как и при вращательном соединении, при создании соединения с помощью функции `Initialize()` смещение призматического соединения равно нулю. Поэтому убедитесь, что между нижним и верхним пределами смещения находится ноль.

Использование призматического соединения аналогично использованию вращательного соединения. Вот соответствующие функции-члены:

```C#
float PrismaticJoint.GetJointTranslation();
float PrismaticJoint.GetJointSpeed();
float PrismaticJoint.GetMotorForce();
void PrismaticJoint.SetMotorSpeed(float speed);
void PrismaticJoint.SetMotorForce(float force);
```

### Соединение шкива (Pulley Joint)

Для создания идеализированного блока используется шкив. Блок соединяет два тела с землей и друг с другом. Когда одно тело поднимается, другое опускается. Общая длина троса блока сохраняется в соответствии с первоначальной конфигурацией.

```
length1 + length2 == constant
```

Вы можете задать передаточное отношение, имитирующее блок и захват. Это приводит к тому, что одна сторона шкива выдвигается быстрее, чем другая. В то же время усилие натяжения с одной стороны меньше, чем с другой. Это можно использовать для создания механического рычага.

```
length1 + ratio * length2 == constant
```

Например, если соотношение равно 2, то длина 1 будет изменяться в два раза быстрее, чем длина 2. Кроме того, сила натяжения веревки, прикрепленной к телу 1, будет в два раза меньше силы натяжения веревки, прикрепленной к телу 2.

![](./Pulley_Joint.png)

Шкивы могут создавать проблемы, когда одна сторона полностью вытянута. Канат с другой стороны будет иметь нулевую длину. В этот момент уравнения ограничения становятся сингулярными (неправильными). Для предотвращения этого следует настроить формы столкновений.

Вот пример определения шкива:

```C#
Vector2 anchor1 = myBody1.GetWorldCenter();
Vector2 anchor2 = myBody2.GetWorldCenter();

Vector2 groundAnchor1 = new Vector2(p1.X, p1.Y + 10.0f);
Vector2 groundAnchor2 = new Vector2(p2.X, p2.Y + 12.0f);
float ratio = 1.0f;

PulleyJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, anchor1, anchor2, ratio);
```

Соединения шкивов обеспечивают текущую длину.

```C#
float PulleyJoint.GetLengthA();
float PulleyJoint.GetLengthB();
```

### Шестеренчатое соединение (Gear Joint)

Если вы хотите создать сложное механическое устройство, возможно, вам захочется использовать шестеренки. В принципе, вы можете создавать шестеренки в Box2D, используя сложные формы для моделирования зубьев шестерни. Это не очень эффективно и может быть утомительно для автора. Вы также должны быть осторожны, выстраивая шестерни в линию, чтобы зубья плавно входили в зацепление. В Box2D есть более простой способ создания шестерен: зубчатое соединение.

![](./Gear_Joint.png)

Зубчатое соединение может соединять только поворотные и/или призматические соединения.

Как и передаточное число шкива, вы можете указать передаточное число. Однако в этом случае передаточное число может быть отрицательным. Также имейте в виду, что если одно соединение является вращающимся (угловым), а другое - призматическим (поступательным), то передаточное число будет состоять из единиц длины или единицы по длине.

```
coordinate1 + ratio * coordinate2 == constant
```

Вот пример зубчатого соединения. Тела `myBodyA` и `myBodyB` - это любые тела из двух соединений, если только они не являются одними и теми же телами.

```C#
GearJointDef jointDef;
jointDef.BodyA = myBodyA;
jointDef.BodyB = myBodyB;
jointDef.Joint1 = myRevoluteJoint;
jointDef.Joint2 = myPrismaticJoint;
jointDef.Ratio = 2.0f * MathF.PI / myLength;
```

Обратите внимание, что зубчатое соединение зависит от двух других соединений. Это создает нестабильную ситуацию. Что произойдет, если удалить эти соединения?

_Внимание: Всегда удаляйте зубчатые соединения перед вращающимися/призматическими соединениями на зубчатых колесах. В противном случае ваш код будет работать неправильно из-за потерянных указателей на зубчатом соединении. Вам также следует удалить зубчатое соединение перед удалением любых задействованных элементов._

### Мышиный шарнир (Mouse Joint)

Мышиный шарнир используется в тестовом стенде для манипулирования телами с помощью мыши. Он пытается переместить точку на теле в направлении текущего положения курсора. Ограничений на вращение нет.

Определение шарнира мыши содержит целевую точку, максимальное усилие, частоту и коэффициент демпфирования. Целевая точка изначально совпадает с точкой крепления тела. Максимальное усилие используется для предотвращения резких реакций при взаимодействии нескольких динамичных тел. Вы можете увеличить его на сколько угодно. Частота и коэффициент демпфирования используются для создания эффекта пружины/демпфера, аналогичного дистанционному соединению.

Многие пользователи пытались адаптировать шарнир мыши для игры. Пользователи часто хотят добиться точного позиционирования и мгновенной реакции. В этом контексте шарнир мыши работает не очень хорошо. Возможно, вы захотите использовать вместо него кинематические элементы.

### Колесный шарнир (Wheel Joint)

Колесный шарнир ограничивает точку на теле В линией на теле А. Колесный шарнир также обеспечивает подвеску.

![](./Wheel_Joint.png)

Шарнир колеса разработан специально для транспортных средств. Он обеспечивает перемещение и поворот. Механизм перемещения снабжен пружиной и демпфером, имитирующими подвеску автомобиля. Поворот позволяет колесу вращаться. Вы можете указать вращательный двигатель для приведения колеса в движение и торможения.

### Сварное соединение (Weld Joint)

Сварное соединение пытается ограничить все относительные перемещения между двумя телами. Посмотрите на испытательном стенде, чтобы увидеть, как ведет себя сварное соединение.

Есть соблазн использовать сварное соединение для определения разрушаемых конструкций. Однако решение Box2D является итеративным, поэтому соединения получаются немного мягкими. Поэтому цепочки элементов, соединенных сварными швами, будут изгибаться.

Вместо этого лучше создавать хрупкие элементы, начиная с одного элемента с несколькими креплениями. Когда тело ломается, вы можете уничтожить крепление и воссоздать его на новом теле. Смотрите пример с возможностью разрушения на испытательном стенде.

### Канатное соединение (Rope Joint)

Канатное соединение ограничивает максимальное расстояние между двумя точками. Это может быть полезно для предотвращения растяжения цепочек тел даже при высокой нагрузке.

### Фрикционное соединение (Friction Joint)

Фрикционное соединение используется для симуляции трения. Соединение обеспечивает двухмерное поступательное трение и угловое трение.

### Моторное соединение (Motor Joint)

Моторный шарнир позволяет управлять движением тела, задавая заданное положение и смещение вращения. Вы можете установить максимальное усилие двигателя и крутящий момент, которые будут приложены для достижения заданного положения и поворота. Если тело заблокировано, оно остановится, и контактные усилия будут пропорциональны максимальному усилию двигателя и крутящему моменту.

## Контакты (Contacts)

Контакты - это объекты, созданные Box2D для управления конфликтами между двумя телами. Если у тела есть дочерние элементы, такие как форма цепочки, то для каждого соответствующего дочернего элемента существует контакт. Существуют различные типы контактов, производные от `Contact`, для управления контактами между различными типами тел. Например, существует класс для управления столкновением многоугольников и другой класс для управления столкновением окружностей.

Вот некоторая терминология, связанная с контактами.

#### contact point

Точка соприкосновения - это точка, в которой соприкасаются две фигуры. Box2D приближает контакт к небольшому числу точек.

#### contact normal

Нормаль к контакту - это единичный вектор, который указывает от одной фигуры к другой. По общему правилу, нормаль указывает от `fixtureA` к `fixtureB`.

#### contact separation

Разделение противоположно проникновению. Разделение является отрицательным, когда фигуры накладываются друг на друга. Возможно, в будущих версиях Box2D будут создаваться точки соприкосновения с положительным разделением, поэтому вы можете захотеть отметить этот знак, когда будете сообщать о точках соприкосновения.

#### contact manifold

Контакт между двумя выпуклыми многоугольниками может привести к образованию до 2 точек контакта. Обе эти точки используют одну и ту же нормаль, поэтому они сгруппированы в контактное многообразие, которое является приближением непрерывной области контакта.

#### normal impulse

Нормальная сила - это сила, приложенная к точке контакта, чтобы предотвратить проникновение фигур внутрь. Для удобства Box2D работает с импульсами. Нормальный импульс - это просто нормальная сила, умноженная на временной шаг.

#### tangent impulse

Касательная сила создается в точке контакта для симуляции трения. Для удобства это значение сохраняется в виде импульса.

#### contact ids

Box2D пытается повторно использовать силу контакта, полученную на основе временного шага, в качестве начальной оценки для следующего временного шага. Box2D использует идентификаторы контактов для сопоставления точек контакта на разных временных шагах. Идентификаторы содержат индексы геометрических объектов, которые помогают отличить одну точку контакта от другой.

Контакты создаются, когда `AABB` двух тел перекрываются. Иногда фильтрация коллизий предотвращает создание контактов. Контакты уничтожаются, когда `AABB` перестают перекрываться.

Таким образом, вы можете сделать вывод, что могут быть созданы контакты для тел, которые не соприкасаются (только их `AABB`). Что ж, это правильно. Это проблема типа "курица или яйцо". Мы не знаем, нужен ли нам объект контакта, пока он не будет создан для анализа столкновения. Мы могли бы сразу удалить контакт, если фигуры не соприкасаются, или мы можем просто подождать, пока `AABB` перестанут перекрываться. Box2D использует последний подход, поскольку он позволяет системе кэшировать информацию для повышения производительности.

### Класс контакта (Contact Class)

Как упоминалось ранее, класс `Contact` создается и уничтожается Box2D. Объекты `Contact` не создаются пользователем. Однако вы можете получить доступ к классу `Contact` и взаимодействовать с ним.

Вы можете получить доступ к необработанному контактному коллектору:

```C#
Manifold Contact.GetManifold();
```

Потенциально вы можете изменить многообразие, но обычно это не поддерживается и предназначено для расширенного использования.

Существует вспомогательная функция для получения `WorldManifold`:

```C#
void Contact.GetWorldManifold(out WorldManifold worldManifold);
```

При этом используются текущие положения тел для вычисления мировых позиций точек соприкосновения.

Сенсоры не создают коллекторов, поэтому для них используют:

```C#
bool touching = sensorContact.IsTouching();
```

Эта функция также работает для тел, не использующих датчики.

Вы можете получить информацию о креплениях от контакта. Из них вы можете получить тела.

```C#
Fixture fixtureA = myContact.GetFixtureA();
Body bodyA = fixtureA.GetBody();
MyActor actorA = (MyActor)bodyA.GetUserData();
```

Вы можете отключить контакт. Это работает только внутри события `ContactListener.PreSolve`, описанного ниже.

### Доступ к контактам (Accessing Contacts)

Вы можете получить доступ к контактам несколькими способами. Вы можете получить доступ к контактам непосредственно в классах мира и тела. Вы также можете реализовать прослушиватель контактов.

Вы можете перебирать все контакты в мире:

```C#
for (Contact c = myWorld.GetContactList(); c != null; c = c.GetNext())
{
    // обработка c
}
```

Вы также можете выполнить итерацию по всем контактам на теле. Они сохраняются в виде графика с использованием класса контактных ребер.

```C#
for (ContactEdge ce = myBody.GetContactList(); ce != null; ce = ce.Next)
{
    Contact c = ce.Contact;
    // обработка c
}
```

Вы также можете получить доступ к контактам с помощью прослушивателя контактов, который описан ниже.

_Внимание: При доступе к контактам из `World` и `Body` могут быть пропущены некоторые временные контакты, которые возникают в середине временного шага. Используйте `ContactListener` для получения наиболее точных результатов._

### Прослушка контакта (Contact Listener)

Вы можете получать контактные данные, реализовав `ContactListener`. Прослушиватель контактов поддерживает несколько событий: начало, завершение, предварительное решение и последующее решение.

```C#
public class MyContactListener : ContactListener
{
    public override void BeginContact(in Contact contact)
    {
        // handle begin event
    }

    public override void EndContact(in Contact contact)
    {
        // handle end event
    }

    public override void PostSolve(in Contact contact, in ContactImpulse impulse)
    {
        // handle pre-solve event
    }

    public override void PreSolve(in Contact contact, in Manifold oldManifold)
    {
        // handle post-solve event
    }
}
```

_Внимание: Не сохраняйте ссылки, отправленные в `ContactListener`. Вместо этого сделайте глубокую копию данных контактной точки в свой собственный буфер. В примере ниже показан один из способов сделать это._

Во время выполнения вы можете создать экземпляр прослушивателя и зарегистрировать его в `World.SetContactListener`. Убедитесь, что ваш прослушиватель остается в области видимости, пока существует объект мира.

#### Begin Contact Event

Вызывается, когда два объекта начинают перекрываться. Это вызывается для датчиков и не-датчиков. Это событие может произойти только внутри временного шага.

#### End Contact Event

Вызывается, когда два объекта перестают перекрываться. Это вызывается для датчиков и не-датчиков. Это может быть вызвано при разрушении объекта, поэтому это событие может произойти за пределами временного шага.

#### Pre-Solve Event

Вызывается после обнаружения коллизий, но до разрешения коллизий. Это дает вам возможность отключить контакт на основе текущей конфигурации. Например, вы можете реализовать одностороннюю платформу, используя этот обратный вызов и вызывая `Contact.SetEnabled(false)`. Контакт будет повторно активирован каждый раз в процессе обработки коллизий, поэтому вам нужно будет отключать контакт на каждом временном шаге. Событие предварительного решения может запускаться несколько раз на каждом временном шаге для каждого контакта из-за непрерывного обнаружения коллизий.

```C#
public override void PreSolve(in Contact contact, in Manifold oldManifold)
{
    WorldManifold worldManifold;
    contact.GetWorldManifold(out worldManifold);
    if (worldManifold.Normal.Y < -0.5f)
    {
        contact.SetEnabled(false);
    }
}
```

Событие предварительного решения также является хорошим средством для определения состояния точки и скорости приближения столкновений.

```C#
WorldManifold worldManifold;
contact.GetWorldManifold(out worldManifold);

PointState[] state1;
PointState[] state2;
Global.GetPointStates(out state1, out state2, oldManifold, contact.GetManifold());

if (state2[0] == PointState.Add)
{
    Body bodyA = contact.GetFixtureA().GetBody();
    Body bodyB = contact.GetFixtureB().GetBody();
    Vector2 point = worldManifold.Points[0];
    Vector2 vA = bodyA.GetLinearVelocityFromWorldPoint(point);
    Vector2 vB = bodyB.GetLinearVelocityFromWorldPoint(point);

    float approachVelocity = Vector2.Dot(vB - vA, worldManifold.Normal);

    if (approachVelocity > 1.0f)
    {
        MyPlayCollisionSound();
    }
}
```

#### Post-Solve Event

Событие post-solve - это то место, где вы можете собирать результаты импульсов столкновения. Если вас не волнуют импульсы, вам, вероятно, следует просто реализовать событие pre-solve.

Есть соблазн реализовать игровую логику, которая изменяет мир физики, внутри обратного вызова контакта. Например, у вас может возникнуть столкновение, которое наносит ущерб и пытается уничтожить связанный с ним объект и его твердое тело. Однако Box2D не позволяет изменять физический мир внутри обратного вызова, поскольку вы можете уничтожить объекты, которые Box2D обрабатывает в данный момент, что приведет к потере указателей.

Рекомендуемая практика обработки контактных данных заключается в буферизации всех контактных данных, которые вас интересуют, и обработке их после временного шага. Вы всегда должны обрабатывать контактные данные сразу после временного шага; в противном случае какой-либо другой клиентский код может изменить физический мир, что приведет к аннулированию буфера контактов. Когда вы обрабатываете контактный буфер, вы можете изменить мир физики, но вам все равно нужно быть осторожным, чтобы не потерять указатели, хранящиеся в буфере контактных точек. На тестовом стенде есть примеры обработки контактных точек, которые не содержат потерянных указателей.

### Фильтрация контактов (Contact Filtering)

Часто в игре вы не хотите, чтобы все объекты сталкивались. Например, вы можете захотеть создать дверь, через которую могут проходить только определенные персонажи. Это называется фильтрацией контактов, потому что некоторые взаимодействия отфильтровываются.

Box2D позволяет вам выполнять пользовательскую фильтрацию контактов, реализуя класс `ContactFilter`. Этот класс требует, чтобы вы реализовали функцию `ShouldCollide`, которая получает два указателя Shape. Ваша функция возвращает значение `true`, если фигуры должны сталкиваться.

Реализация `ShouldCollide` по умолчанию использует `FilterData`.

```C#
public virtual bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
{
    Filter filterA = fixtureA.Filter;
    Filter filterB = fixtureB.Filter;

    if (filterA.GroupIndex == filterB.GroupIndex && filterA.GroupIndex != 0)
    {
        return filterA.GroupIndex > 0;
    }

    bool collide = (filterA.MaskBits & filterB.CategoryBits) != 0 && (filterA.CategoryBits & filterB.MaskBits) != 0;
    return collide;
}
```

Во время выполнения вы можете создать экземпляр своего фильтра контактов и зарегистрировать его в `World::SetContactFilter`. Убедитесь, что ваш фильтр остается в области видимости, пока существует `world`.

```C#
MyContactFilter filter;
world.SetContactFilter(filter);
```

## Мир (World)

Класс `World` содержит тела и соединения. Он управляет всеми аспектами моделирования и позволяет выполнять асинхронные запросы (например, запросы `AABB` и бросание лучей). Большая часть ваших взаимодействий с Box2D будет осуществляться с помощью объекта `World`.

### Создание и разрушение мира (Creating and Destroying a World)

Создать мир довольно просто. Вам просто нужно указать вектор силы тяжести.

```C#
World myWorld = new World(gravity);

// ... делаем работу ...

myWorld = null;
```

### Использование мира (Using a World)

Класс `World` содержит фабрики по созданию и уничтожению тел и соединений. Эти фабрики рассматриваются далее в разделах, посвященных телам и соединениям. Есть и другие взаимодействия с `World`, о которых я расскажу сейчас.

### Симуляция (Simulation)

Для моделирования используется мировой класс. Вы указываете временной шаг и количество итераций по скорости и местоположению. Например:

```C#
float timeStep = 1.0f / 60.0f;
int velocityIterations = 10;
int positionIterations = 8;
myWorld.Step(timeStep, velocityIterations, positionIterations);
```

После выполнения временного шага вы можете изучить свои тела и соединения для получения информации. Скорее всего, вы будете фиксировать положение тел, чтобы обновлять персонажей и отрисовать их. Вы можете выполнять временной шаг в любом месте игрового цикла, но вам следует помнить о порядке вещей. Например, вы должны создать тела перед временным шагом, если хотите получить результаты коллизий для новых тел в этом кадре.

Как я уже говорил выше в руководстве по `HelloWorld`, вам следует использовать фиксированный временной шаг. Используя больший временной шаг, вы можете повысить производительность в сценариях с низкой частотой кадров. Но, как правило, вам следует использовать временной шаг не более 1/30 секунды. Временной шаг в 1/60 секунды обычно обеспечивает высокое качество моделирования.

Количество итераций определяет, сколько раз решатель ограничений обрабатывает все контакты и соединения в мире. Большее количество итераций всегда приводит к лучшему моделированию. Но не меняйте маленький временной шаг на большое количество итераций. Частота 60 Гц и 10 повторений намного лучше, чем частота 30 Гц и 20 повторений.

После выполнения шага вам следует снять все усилия, которые вы прикладывали к своим телам. Это делается с помощью команды `World.ClearForces`. Это позволяет вам выполнять несколько подэтапов с одним и тем же силовым полем.

```C#
myWorld.ClearForces();
```

### Исследование мира (Exploring the World)

Мир - это контейнер для тел, контактов и соединений. Вы можете выбрать списки тел, контактов и соединений в мире и просмотреть их. Например, этот код открывает доступ ко всем телам в мире:

```C#
for (Body b = myWorld.GetBodyList(); b != null; b = b.GetNext())
{
    b.SetAwake(true);
}
```

К сожалению, реальные программы могут быть более сложными. Например, следующий код не работает:

```C#
for (Body b = myWorld.GetBodyList(); b != null; b = b.GetNext())
{
    GameActor myActor = (GameActor)b.UserData;
    if (myActor.IsDead())
    {
        myWorld.DestroyBody(b);
    }
}
```

Все идет нормально, пока тело не будет уничтожено. Как только тело будет уничтожено, его следующая ссылка станет недействительной. Решение этой проблемы заключается в копировании следующей ссылки перед уничтожением тела.

```C#
Body node = myWorld.GetBodyList();
while (node != null)
{
    Body b = node;
    node = node.GetNext();

    GameActor myActor = (GameActor)b.UserData;
    if (myActor.IsDead())
    {
        myWorld.DestroyBody(b);
    }
}
```

Это безопасно уничтожит текущее тело. Однако вы можете захотеть вызвать игровую функцию, которая может уничтожить несколько тел. В этом случае вам нужно быть очень осторожным. Решение зависит от конкретного приложения, но для удобства я покажу один способ решения проблемы.

```C#
Body node = myWorld.GetBodyList();
while (node != null)
{
    Body b = node;
    node = node.GetNext();

    GameActor myActor = (GameActor)b.UserData;
    if (myActor.IsDead())
    {
        bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b);
        if (otherBodiesDestroyed)
        {
            node = myWorld.GetBodyList();
        }
    }
}
```

Очевидно, что для того, чтобы это сработало, игра `CrazyBodyDestroyer` должна быть честной в отношении того, что она уничтожила.

### Запросы AABB (AABB Queries)

Иногда требуется определить все фигуры в регионе. В классе `World` есть метод быстрого логарифмирования для этого, использующий структуру данных с широкой фазой. Вы предоставляете `AABB` в мировых координатах и реализацию `QueryCallback`. Система `World` вызывает ваш метод для каждого объекта, `AABB` которого перекрывает запрос `AABB`. Для продолжения запроса верните значение `true`, в противном случае верните значение `false`. Например, следующий код находит все объекты, которые потенциально пересекаются с указанным `AABB`, и запускает все связанные объекты.

```C#
myWorld.QueryAABB(fixture =>
{
    Body body = fixture.GetBody();
    body.SetAwake(true);

    // Return true to continue the query.
    return true;
}, aabb);
```

Вы не можете делать никаких предположений о порядке обратных вызовов.

### Броски лучей (Ray Casts)

Вы можете использовать бросание лучей для проверки прямой видимости, стрельбы из оружия и т.д. Вы выполняете бросание лучей, реализуя метод обратного вызова и указывая начальную и конечную точки. Класс `World` вызывает ваш класс при каждом попадании луча на объект. В вашем ответном вызове указывается точка пересечения, единичный вектор нормали и дробное расстояние вдоль луча. Вы не можете делать никаких предположений о порядке выполнения обратных вызовов.

Вот пример:

```C#
Vector2 point1 = new Vector2(-1.0f, 0.0f);
Vector2 point2 = new Vector2(3.0f, 1.0f);
myWorld.RayCast((fixture, point, normal, fraction) =>
{
    // ...
}, point1, point2);
```

_Внимание: Из-за ошибок округления в вашей статической среде лучи могут просачиваться через небольшие щели между полигонами. Если это неприемлемо в вашем приложении, попробуйте слегка перекрывать полигоны._
